/**
 * @version 1.0
 * @created 03 December 2010
 *
 * AuctionServiceImplementor
 *
 * Copyright @ 2010 by CES
 * All Rights Reserved.
 * CES CONFIDENTIAL AND TRADE SECRET
 * 
 */
package com.ces.knacky.services.implementor;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;

import com.ces.knacky.framework.exception.DataAccessException;
import com.ces.knacky.framework.services.implementor.CommonServiceImplementor;
import com.ces.knacky.model.Auction;
import com.ces.knacky.model.AuctionStatus;
import com.ces.knacky.model.Bid;
import com.ces.knacky.model.BidWorkflowAudit;
import com.ces.knacky.model.BidWorkflowStatus;
import com.ces.knacky.model.Flag;
import com.ces.knacky.model.Item;
import com.ces.knacky.model.OrderStatus;
import com.ces.knacky.model.OrderTable;
import com.ces.knacky.model.UserTable;
import com.ces.knacky.services.AuctionService;
import com.ces.knacky.services.CheckoutService;
import com.ces.knacky.services.InventoryService;

/**
 * Concrete implementation for @AuctionService interface.
 * @author shivangi_walvekar
 *
 */
public class AuctionServiceImplementor extends CommonServiceImplementor implements AuctionService{
	
	private CheckoutService checkoutService;
	
	@Autowired
	private InventoryService inventoryService;


	/**
	 * It is used to place the bid for the selected item and auction.
	 *  
	 * @param 	itemId - Represents persistent id of Item table for whom the bid is to be placed.
	 * 			auctionDescription - Represents the persistent description of the Auction for which 
	 * 			the bid is to be placed.
	 * 			bidPrice - price quoted by the user for particular auction item.
	 * @return	result -  
	 * 			true when bid placement is successful,false otherwise.
	 */
	public boolean placeBid(int itemId,int auctionId,Float bidPrice) {
			Bid bid = new Bid();
			bid.setCreateTime(new Date());
			bid.setUpdateTime(new Date());
			bid.setAmount(bidPrice);
			Item item = (Item) load(Item.class,itemId);
			if(item == null){
				// TODO - log and throw 
				System.out.println("Null item : "+ itemId);
			}
			System.out.println("item:  "+item.getDescription());
			bid.setItem(item);					
			/*Load the auction*/			
			Auction auction = (Auction)load(Auction.class, auctionId);
			if(auction == null){
				// TODO - log and throw
				System.out.println("null auction : "+auctionId);
			}
			bid.setAuction(auction);			
			/* Get the logged-in user.*/
			UserTable user = (UserTable) findFirstByDescription(
					UserTable.class, SecurityContextHolder.getContext().getAuthentication().getName());
			if (user == null) {
				// TODO - log & throw application specific exception
			}
			bid.setUser(user);			
			/* Get the 'ACTIVE' flag object.*/
			Flag activeFlag = (Flag) findFirstByDescription(
					Flag.class, "ACTIVE");
			if (activeFlag == null) {
				// TODO - log & throw application specific exception
				System.out.println("flag null...");
			}
			bid.setFlag(activeFlag);
			
			/* set the bid workflow status to PLACED*/
			BidWorkflowStatus bidStatus = (BidWorkflowStatus) findFirstByDescription
			(BidWorkflowStatus.class, "Placed");
			if(bidStatus == null) {
				// TODO - log & throw application specific exception
			}			
			/*Create bid_workflow_audit entries*/
			Set<BidWorkflowAudit> bidWorkFlowAuditSet = new HashSet<BidWorkflowAudit>();
			BidWorkflowAudit bidWorkFlowAudit = new BidWorkflowAudit();
			bidWorkFlowAudit.setBid(bid);
			bidWorkFlowAudit.setItem(item);
			bidWorkFlowAudit.setUser(user);
			bidWorkFlowAudit.setCreateTime(new Date());
			bidWorkFlowAudit.setUpdateTime(new Date());
			bidWorkFlowAudit.setBidWorkflowStatus(bidStatus);
			bidWorkFlowAudit.setComments("Bid placed by user Id : "+ user.getId());
			bidWorkFlowAudit.setFlag(activeFlag);
			bidWorkFlowAuditSet.add(bidWorkFlowAudit);
			bid.setBidWorkFlowAudits(bidWorkFlowAuditSet);			
			boolean result =  add(bid);	
		return result;
	}

	/**
	 * This method approves the bids for bid Ids. For approved bids,it issues
	 * a place order request.
	 * @param selectedBidIds - selected work flow bid ids to approve
	 * @return - true if approved successfully
	 * @throws DataAccessException 
	 */
	// TODO - transaction mgmt and flushing the data only once.
	public boolean approveBid(String selectedBidIds){
		if(selectedBidIds == null){
			return false;
		}		
		BidWorkflowStatus bidStatusApproved = (BidWorkflowStatus) findFirstByDescription
		(BidWorkflowStatus.class, "Approved");
		/* Get the admin user.*/
		// TODO - put the user in session and use it.
		UserTable user = (UserTable) findFirstByDescription(
				UserTable.class, SecurityContextHolder.getContext().getAuthentication().getName());		
		Flag flagActive = (Flag) findFirstByDescription(Flag.class, "ACTIVE");
		AuctionStatus auctionStatus = (AuctionStatus) findFirstByDescription(AuctionStatus.class,
		"CLOSED");
		
		List<Bid> bids = (List<Bid>) list("SELECT DISTINCT bid from Bid bid,BidWorkflowAudit" +
		" bidWorkflowAudit where bid.id = bidWorkflowAudit.bid.id and bidWorkflowAudit.flag.id = 1"
		+ " and bidWorkflowAudit.bidWorkflowStatus.id = 1 and bid.flag.id = 1 and bid.id IN ("
		+selectedBidIds + " )");		
		
		for(Bid bid : bids){
			/* Fetch all other bid audits mark them as INACTIVE.*/
			Iterator<BidWorkflowAudit> bidAuditIterator = bid.getBidWorkFlowAudits().iterator();
			String statementForAudit = "UPDATE BidWorkflowAudit bidWorkflowAudit" +
			" SET bidWorkflowAudit.flag.id = 2 where bidWorkflowAudit.id IN ( ";
			StringBuffer strAuditIds = new StringBuffer("");
			while(bidAuditIterator.hasNext()){
				BidWorkflowAudit bAudit = (BidWorkflowAudit) bidAuditIterator.next();
				strAuditIds.append(bAudit.getId()).append(",");
			}
			strAuditIds = strAuditIds.deleteCharAt(strAuditIds.length()-1);		
			statementForAudit = statementForAudit.concat(strAuditIds.toString()).concat(" )");
			System.out.println("statementForAudit = "+statementForAudit);			
			batchUpdate(statementForAudit);
			
			/* Fetch all other bids for this auctionItem and mark those bids as INACTIVE.*/
			String statement = "UPDATE Bid bid SET bid.flag.id = 2 where bid.item.id = " + 
			bid.getItem().getId() + " AND bid.id IN ( ";
			StringBuffer strIds = new StringBuffer("");
			Iterator<Bid> iterator = bid.getAuction().getBids().iterator();
			while(iterator.hasNext()){
				Bid b = (Bid) iterator.next();
				strIds.append(b.getId()).append(",");
			}
			strIds = strIds.deleteCharAt(strIds.length()-1);			
			statement = statement.concat(strIds.toString()).concat(" )");
			System.out.println("statement = "+statement);
			batchUpdate(statement);
			
			BidWorkflowAudit bidWorkFlowAudit = new BidWorkflowAudit();
			bidWorkFlowAudit.setBid(bid);
			bidWorkFlowAudit.setBidWorkflowStatus(bidStatusApproved);
			bidWorkFlowAudit.setUser(user);
			bidWorkFlowAudit.setCreateTime(new Date());
			bidWorkFlowAudit.setUpdateTime(new Date());
			bidWorkFlowAudit.setItem(bid.getItem());
			/* Set ACTIVE flag for the new bidworkflowAudit.*/
			bidWorkFlowAudit.setFlag(flagActive);		
			// TODO - add a text box in UI to input comment.
			bidWorkFlowAudit.setComments("Bid Approved by user id : "+user.getId());
			bid.getBidWorkFlowAudits().add(bidWorkFlowAudit);
			
			int	orderID = getCheckoutService().placeOrder(bid.getItem().getId().toString());
			System.out.println("Order created : "+ orderID + " for bidId = "+bid.getId());
			/* Update bid table with order.*/
			OrderTable order = (OrderTable) load(OrderTable.class,orderID);
			bid.setOrder(order);
			update(bid);			
			
			/*
			 * TODO - invoke checkout process to check for item availability. It
			 * item available, forward PO to order fulfillment process. Else
			 * notify customer about order delay.
			 */
			 try {
				if(getInventoryService().isOrderDelayed(orderID)){
					System.out.println("Order :"+ orderID  + " is delayed" );
					// TODO - notify customer about order delay.
				}				
			} catch (DataAccessException e) {
				// TODO				
				e.printStackTrace();
			}
			}
		return true;
	}		

	/**
	 * @return - list of bids having placed as status
	 */
	public List<Bid> loadPlacedBid() {
		List<Bid> iEntities = (List<Bid>) list("SELECT DISTINCT bid from Bid bid,BidWorkflowAudit " +
		"bidWorkflowAudit where bid.id = bidWorkflowAudit.bid.id and bidWorkflowAudit.flag.id = 1"
		+ " and bidWorkflowAudit.bidWorkflowStatus.id = 1 and bid.flag.id = 1 order by" +
		" bid.createTime,bid.amount desc");
		System.out.println("Size of placedBid in AuctionService : "+iEntities.size());
		return iEntities;
	}

	/** This method cancels the placed bids for the selected bid Ids.
	 * @param selectedIds - selected bid ids to cancel
	 * @return - true if canceled successfully
	 */
	@Override
	public boolean cancelBids(String selectedIds) {
		BidWorkflowStatus bidWorkflowStatus = (BidWorkflowStatus) findFirstByDescription
		(BidWorkflowStatus.class, "Cancelled");
		Flag  flagInactive = (Flag) findFirstByDescription(Flag.class,"INACTIVE");
		Flag  flagActive = (Flag) findFirstByDescription(Flag.class,"ACTIVE");
		OrderStatus orderStatus = (OrderStatus) findFirstByDescription(OrderStatus.class,
		"Cancelled");
		
		UserTable user = (UserTable) findFirstByDescription(UserTable.class, 
		SecurityContextHolder.getContext().getAuthentication().getName());
		
		String [] selectedBidIds =  selectedIds.split(",");
		List<String> stringBidIds = Arrays.asList(selectedBidIds);
		
		for(String strBidId : stringBidIds){
			System.out.println("strBidId = "+strBidId);
			Bid bid = (Bid) load(Bid.class,Integer.parseInt(strBidId));
			bid.setFlag(flagInactive);
			update(bid);
			/* Get workflow audits for this bid*/
			List<BidWorkflowAudit> bidWorkflowAuditList = (List<BidWorkflowAudit>) 
			list("SELECT bidWorkflowAudit from BidWorkflowAudit bidWorkflowAudit where" +
			" bidWorkflowAudit.bid.id = "+ bid.getId());
			/* Set the flag of all the bid audits to INACTIVE.*/
			for(BidWorkflowAudit bidWorkflowAudit : bidWorkflowAuditList){
				bidWorkflowAudit.setFlag(flagInactive);
				update(bidWorkflowAudit);
			}
			/* Create a new bid audit record for cancelled status. */
			BidWorkflowAudit newBidAudit = new BidWorkflowAudit();
			newBidAudit.setBid(bid);
			newBidAudit.setBidWorkflowStatus(bidWorkflowStatus);
			newBidAudit.setComments("Cancelled by user " + user.getId());
			newBidAudit.setCreateTime(new Date());
			newBidAudit.setUpdateTime(new Date());
			newBidAudit.setFlag(flagActive);
			newBidAudit.setItem(bid.getItem());
			newBidAudit.setUser(user);
			add(newBidAudit);
			
			/* Cancel PO for this bid*/
			if(bid.getOrder() != null){
				OrderTable order = bid.getOrder();
				order.setOrderStatus(orderStatus);
				order.setFlag(flagInactive);
				update(order);
			}
		}	
		
		
		
		/* TODO - 1)Update PO with cancel request 2) Sends updated PO to 
		Order-to-Cash (Workflow) service to cancel 3) Refunds payment through PayPal Service */
		return false;
	}

	/**
	 * @param - userId - persistent id of the userTable object.
	 * @return - myBidList - list of bids having for the specified userId.
	 */
	@Override
	public List<Bid> loadMyBids(Integer userId) {
		System.out.println("Fetching bids for user ID = "+userId);
		List<Bid> myBids = (List<Bid>) list("SELECT distinct bid from Bid bid,BidWorkflowAudit bidAudit" +
		" where bid.id = bidAudit.bid.id and bid.flag.id = 1 and bid.user.id ="+userId);
		System.out.println("Size of myBids = "+myBids.size());
		return myBids;
	}
	
	/**
	 * Returns checkoutService object used during dependency injection.
	 * 
	 * @return	checkoutService
	 * 			Represents checkoutService object initialized using dependency injection.
	 */
	public CheckoutService getCheckoutService() {
		return checkoutService;
	}

	/**
	 * It is used to set the checkoutService object.
	 *  
	 * @param 	checkoutService
	 * 			Represents checkoutService object initialized using dependency injection.
	 */
	public void setCheckoutService(CheckoutService checkoutService) {
		this.checkoutService = checkoutService;
	}

	@Override
	public boolean cancelApprovedBids(List<BidWorkflowAudit> approvedBids) {
		if(approvedBids == null || (approvedBids.size() == 0)){
			System.out.println("No approved bids to cancel.");
			// TODO - log
			return false;
		}
		OrderStatus orderStatus = (OrderStatus) findFirstByDescription(OrderStatus.class, "Cancelled");
		for(BidWorkflowAudit bidWorkFlowAudit : approvedBids){
			/* Update Order status to Cancelled.*/
			
		}
		
		return false;
	}

	@Override
	public boolean cancelPlacedBids() {
		// TODO Auto-generated method stub
		return false;
	}
	
	/**
	 * It is used to approve bids automatically. It finds all those auctions which are in live state
	 * and approval has taken place. It selects bid with higher value and mark it as approved.
	 */
	@Override
	public void automaticApproveBid() {
		Flag  flagInactive = (Flag) findFirstByDescription(Flag.class,"INACTIVE");

		/*
		 * Approve Bids whose auction is expired
		 */
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
		List<BidWorkflowAudit> bidWorkflowAudits = (List<BidWorkflowAudit>) list("select bidWorkflowAudit from Bid bid, BidWorkflowAudit bidWorkflowAudit where bid.amount IN (select max(b2.amount) from Bid b2 where b2.auction.id=bid.auction.id group by b2.auction.id ) and bid.auction.endDate > '"+ simpleDateFormat.format(new Date()) +"' and bid.id = bidWorkflowAudit.bid.id and bid.flag.id = 1 and bidWorkflowAudit.bidWorkflowStatus.description='Placed'");
		BidWorkflowStatus bidStatusApproved = (BidWorkflowStatus) findFirstByDescription
		(BidWorkflowStatus.class, "Approved");

		for(BidWorkflowAudit bidWorkflowAudit : bidWorkflowAudits){
			bidWorkflowAudit.setBidWorkflowStatus(bidStatusApproved);
			//bidWorkflowAudit.getBid().setFlag(flagInactive);
			bidWorkflowAudit.setFlag(flagInactive);
			update(bidWorkflowAudit);
		}

		/*
		 * Update all rest of bids whose auction is expired as not approved
		 */

		bidWorkflowAudits = (List<BidWorkflowAudit>) list("select bidWorkflowAudit from Bid bid, BidWorkflowAudit bidWorkflowAudit where bid.amount NOT IN (select max(b2.amount) from Bid b2 where b2.auction.id=bid.auction.id group by b2.auction.id ) and bid.auction.endDate > '"+ simpleDateFormat.format(new Date()) +"' and bid.id = bidWorkflowAudit.bid.id and bid.flag.id = 1 and bidWorkflowAudit.bidWorkflowStatus.description='Placed'");
		BidWorkflowStatus bidStatusRejected = (BidWorkflowStatus) findFirstByDescription
		(BidWorkflowStatus.class, "Rejected");


		for(BidWorkflowAudit bidWorkflowAudit : bidWorkflowAudits){
			bidWorkflowAudit.setBidWorkflowStatus(bidStatusRejected);
			//bidWorkflowAudit.getBid().setFlag(flagInactive);
			bidWorkflowAudit.setFlag(flagInactive);
			update(bidWorkflowAudit);
		}
	}
	
	public InventoryService getInventoryService() {
		return inventoryService;
	}

	public void setInventoryService(InventoryService inventoryService) {
		this.inventoryService = inventoryService;
	}
}
